﻿/* Copyright 2011 Teun Duynstee
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.IO;

namespace MongoAspnetProviders
{
    [Obsolete]
    public class DocumentBsonReader : BsonReader
    {
        public DocumentBsonReader(BsonDocument document, string typeName)
        {
            _rootDocument = document;
            _innerList = SerializeToList(document, typeName);
            _position = new Stack<int>();
        }

        private BsonDocument _rootDocument;
        private List<TreeNode> _innerList;
        Stack<int> _position;
        int _currPosition = 1; // skip the root document

        private List<TreeNode> SerializeToList(BsonDocument document, string typeName)
        {
            List<TreeNode> result = new List<TreeNode>();
            AddNodeToList(result, new BsonElement(typeName, document), null);
            return result;
        }

        private TreeNode AddNodeToList(List<TreeNode> list, BsonElement value, TreeNode parent)
        {
            TreeNode newNode = new TreeNode()
            {
                Element = value,
                Parent = parent
            };
            list.Add(newNode);
            newNode.Index = list.Count - 1;
            if (value.Value.IsBsonDocument)
            {
                TreeNode prevSibling = null;
                foreach (var child in value.Value.AsBsonDocument.Elements)
                {
                    TreeNode newChildNode = AddNodeToList(list, child, newNode);
                    if (prevSibling != null)
                    {
                        prevSibling.NextSibling = newChildNode;
                    }
                    prevSibling = newChildNode;
                }
                TreeNode documentClosure = new TreeNode()
                {
                    Element = new BsonElement("_", DocumentClosure.Value),
                    Parent = newNode
                };
                list.Add(documentClosure);
                documentClosure.Index = list.Count - 1;
                if (prevSibling != null)
                {
                    prevSibling.NextSibling = documentClosure;
                }
                prevSibling = documentClosure;
            }
            if (value.Value.IsBsonArray)
            {
                TreeNode prevSibling = null;
                foreach (var child in value.Value.AsBsonArray.Values)
                {
                    TreeNode newChildNode = AddNodeToList(list, new BsonElement("_", child), newNode);
                    if (prevSibling != null)
                    {
                        prevSibling.NextSibling = newChildNode;
                    }
                    prevSibling = newChildNode;
                }
                TreeNode documentClosure = new TreeNode()
                {
                    Element = new BsonElement("_", DocumentClosure.Value),
                    Parent = newNode
                };
                list.Add(documentClosure);
                documentClosure.Index = list.Count - 1;
                if (prevSibling != null)
                {
                    prevSibling.NextSibling = documentClosure;
                }
                prevSibling = documentClosure;
            }
            return newNode;
        }


        public override BsonReaderState State
        {
            get 
            { 
                BsonValue curr = CurrentNode.Element.Value;
                if(curr.IsBsonDocument || curr.IsBsonArray)
                {
                    return BsonReaderState.ScopeDocument;
                }
                else if (curr is DocumentClosure)
                {
                    return BsonReaderState.EndOfDocument;
                }
                else{
                    return BsonReaderState.Value;
                }
            }
        }

        public override void Close()
        {
            throw new NotImplementedException();
        }

        public override void Dispose()
        {
            throw new NotImplementedException();
        }

        public override bool FindElement(string name)
        {
            int currentIndex = CurrentNode.Parent.Index + 1;
            while (_innerList[currentIndex].Element.Value.BsonType != BsonType.EndOfDocument)
            {
                if (_innerList[currentIndex].Element.Name == name)
                {
                    // set current position to found location
                    this._currPosition = currentIndex;
                    return true;
                }
                currentIndex = _innerList[currentIndex].NextSibling.Index;
            }

            return false;
        }

        public override string FindString(string name)
        {
            return string.Empty;
        }

        public override BsonType CurrentBsonType
        {
            get 
            {
                return _innerList[_currPosition].Element.Value.BsonType;
            }
        }

        public override BsonType ReadBsonType()
        {
            return this.CurrentBsonType;
        }

        public class DocumentReaderBookmark : BsonReaderBookmark
        {
            public DocumentReaderBookmark(int pos):base(BsonReaderState.ScopeDocument, BsonType.Document, "")
            {
                _position = pos;
            }
            int _position;
            public int Position
            {
                get { return _position; }
            }
        }

        public override BsonReaderBookmark GetBookmark()
        {
            DocumentReaderBookmark bm = new DocumentReaderBookmark(CurrentNode.Index);
            return bm;
        }
        public override void ReturnToBookmark(BsonReaderBookmark bookmark)
        {
            if (bookmark is DocumentReaderBookmark)
            {
                _currPosition = (bookmark as DocumentReaderBookmark).Position;
            }
            else
            {
                throw new InvalidCastException("DocumentBsonReader can only return to Bookmarks of type DocumentReaderBookmark");
            }
        }


        public override string ReadName()
        {
            return CurrentNode.Element.Name;
        }


        public override void ReadBinaryData(out byte[] bytes, out BsonBinarySubType subType)
        {
            subType = CurrentElement.Value.AsBsonBinaryData.SubType;
            bytes = CurrentElement.Value.AsBsonBinaryData.Bytes;
        }
        public override void ReadBinaryData(string expectedName, out byte[] bytes, out BsonBinarySubType subType)
        {
            ValidateName(expectedName);
            ReadBinaryData(out bytes, out subType);
        }

        public override bool ReadBoolean()
        {
            bool value = CurrentElement.Value.AsBoolean;
            MoveNextOrIn();
            return value;
        }


        public override bool ReadBoolean(string expectedName)
        {
            ValidateName(expectedName);
            return ReadBoolean();
        }

        public override DateTime ReadDateTime()
        {
            DateTime value = CurrentElement.Value.AsDateTime;
            MoveNextOrIn();
            return value;
        }

        public override DateTime ReadDateTime(string expectedName)
        {
            ValidateName(expectedName);
            return ReadDateTime();
        }


        public override double ReadDouble()
        {
            double value = CurrentElement.Value.AsDouble;
            MoveNextOrIn();
            return value;
        }

        public override double ReadDouble(string expectedName)
        {
            ValidateName(expectedName);
            return ReadDouble();
        }

        public override void ReadEndArray()
        {
            ReadEndDocument();
        }

        public override void ReadEndDocument()
        {
            while (CurrentElement.Value.BsonType != BsonType.EndOfDocument)
            {
                MoveNextSibling();
            }
            MoveNextSibling();
        }

        public override int ReadInt32()
        {
            int value = CurrentElement.Value.AsInt32;
            MoveNextOrIn();
            return value;
        }

        public override int ReadInt32(string expectedName)
        {
            ValidateName(expectedName);
            return ReadInt32();
        }

        public override long ReadInt64()
        {
            long value = CurrentElement.Value.AsInt64;
            MoveNextOrIn();
            return value;
        }

        public override long ReadInt64(string expectedName)
        {
            ValidateName(expectedName);
            return ReadInt64();
        }

        public override string ReadJavaScript()
        {
            string value = CurrentElement.Value.AsBsonJavaScript.AsString;
            MoveNextOrIn();
            return value;
        }

        public override string ReadJavaScript(string expectedName)
        {
            ValidateName(expectedName);
            return ReadJavaScript();
        }

        public override string ReadJavaScriptWithScope()
        {
            string value = CurrentElement.Value.AsBsonJavaScriptWithScope.AsString;
            MoveNextOrIn();
            return value;
        }

        public override string ReadJavaScriptWithScope(string expectedName)
        {
            ValidateName(expectedName);
            return ReadJavaScriptWithScope();
        }

        public override void ReadMaxKey()
        {
            throw new NotImplementedException();
        }

        public override void ReadMaxKey(string expectedName)
        {
            throw new NotImplementedException();
        }

        public override void ReadMinKey()
        {
            throw new NotImplementedException();
        }

        public override void ReadMinKey(string expectedName)
        {
            throw new NotImplementedException();
        }

        public override void ReadNull()
        {
            MoveNextOrIn();
        }

        public override void ReadNull(string expectedName)
        {
            ValidateName(expectedName);
            ReadNull();
        }

        public override void ReadObjectId(out int timestamp, out int machine, out short pid, out int increment)
        {
            ObjectId value = CurrentElement.Value.AsObjectId;
            timestamp = value.Timestamp;
            machine = value.Machine;
            pid = value.Pid;
            increment = value.Increment;
            MoveNextOrIn();
        }

        public override void ReadObjectId(string expectedName, out int timestamp, out int machine, out short pid, out int increment)
        {
            ValidateName(expectedName);
            ReadObjectId(out timestamp, out machine, out pid, out increment);
        }

        public override void ReadRegularExpression(out string pattern, out string options)
        {
            throw new NotImplementedException();
        }

        public override void ReadRegularExpression(string expectedName, out string pattern, out string options)
        {
            throw new NotImplementedException();
        }

        public override void ReadStartArray()
        {
            if (CurrentElement.Value.IsBsonArray)
            {
                MoveNextOrIn();
            }
            else
            {
                throw new InvalidOperationException("Not at the start of an array");
            }
        }

        public override void ReadStartDocument()
        {
            if (CurrentElement.Value.IsBsonDocument)
            {
                MoveNextOrIn();
            }
            else
            {
                throw new InvalidOperationException("Not at the start of a document");
            }
        }

        public override string ReadString()
        {
            string value = CurrentElement.Value.AsString;
            MoveNextOrIn();
            return value;
        }

        public override string ReadString(string expectedName)
        {
            ValidateName(expectedName);
            return ReadString();
        }

        public override string ReadSymbol()
        {
            string value = CurrentElement.Value.AsBsonSymbol.Name;
            MoveNextOrIn();
            return value;
        }

        public override string ReadSymbol(string expectedName)
        {
            ValidateName(expectedName);
            return ReadSymbol();
        }

        public override long ReadTimestamp()
        {
            long value = CurrentElement.Value.AsBsonTimestamp.Value;
            MoveNextOrIn();
            return value;

        }

        public override long ReadTimestamp(string expectedName)
        {
            ValidateName(expectedName);
            return ReadTimestamp();
        }

        public override void  SkipValue()
        {
            MoveNextSibling();
        }
        public override void SkipName()
        {
            //noop
        }


        private TreeNode CurrentNode
        {
            get { return _innerList[_currPosition]; }
        }
        private BsonElement CurrentElement
        {

            get { return CurrentNode.Element; }
        }
        private void MoveNextOrIn()
        {
            _currPosition++;
        }
        private void MoveNextSibling()
        {
            if (CurrentNode.NextSibling == null)
            {
                if (CurrentNode.Parent == null) return;
                _currPosition = CurrentNode.Parent.Index;
                MoveNextSibling();
            }
            else
            {
                _currPosition = CurrentNode.NextSibling.Index;
            }
        }
        private void ValidateName(string expectedName)
        {
            string name = CurrentElement.Name;
            if (CurrentNode.Parent.Element.Value.IsBsonArray)
            {
                // array elements have no name, so everything goes
                return;
            }
            if (name != expectedName)
            {
                throw new InvalidOperationException("Expected element named " + expectedName);
            }
        }
        public class TreeNode
        {
            public int Index { get; set; }
            public TreeNode Parent { get; set; }
            public TreeNode NextSibling { get; set; }
            public BsonElement Element { get; set; }
        }
        public class DocumentClosure : BsonValue
        {
            private DocumentClosure()
                : base(BsonType.EndOfDocument)
            {
            }
            static private DocumentClosure _instance;
            static DocumentClosure()
            {
                _instance = new DocumentClosure();
            }
            public static DocumentClosure Value { get { return _instance; } }

            public override int CompareTo(BsonValue other)
            {
                return 0;
            }
        }

    }
}
